import backend.Translator;
import frontend.lexer.GramError;
import frontend.lexer.Lexer;
import frontend.lexer.Token;
import frontend.parser.Parser;
import frontend.parser.syntaxtree.CompUnitNode;
import frontend.parser.syntaxtree.SyntaxNode;
import frontend.visitor.Visitor;
import middleware.llvm.Module;
import middleware.optimize.*;
import until.FileConfig;

import java.io.*;
import java.util.ArrayList;
import java.util.Comparator;

public class Compiler {
    static ArrayList<GramError> errorList = new ArrayList<>();

    public static void main(String[] args) {
        try {
            clear_files();
            // 读取输入文件
            File inputFile = new File("testfile.txt");
            InputStream inputStream = new FileInputStream(inputFile);
            InputStream inputStream2 = new FileInputStream(inputFile);
            InputStream inputStream3 = new FileInputStream(inputFile);
            InputStream inputStream4 = new FileInputStream(inputFile);
            InputStream inputStream5 = new FileInputStream(inputFile);
            lexer_analysis(inputStream);
            parse_analysis(inputStream2);
            symbol_analysis(inputStream3);
            if(errorList.isEmpty()) {
                llvm_generate(inputStream4);
                mips_generate(inputStream5, true);
            }
            error_output();
            inputStream.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    static void clear_files() {
        for (FileConfig fileConfig : FileConfig.values()) {
            File file = new File(fileConfig.getPath());
            try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
                writer.write("");
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    static void lexer_analysis(InputStream inputStream) throws IOException {
        Lexer lexer = new Lexer(inputStream);
        File lexerFile = new File(FileConfig.outLexer.getPath());
        StringBuilder lexerSB = new StringBuilder();
        Token token;
        //遍历文件-词法分析
        while (lexer.next()) {
            token = lexer.getToken();
            lexerSB.append(token.getType().getTktype()).append(" ").append(token.getContent()).append("\n");
        }
        errorList.addAll(lexer.getErrorList());
        if (errorList.isEmpty() && FileConfig.outLexer.getOpen()) {
            try (BufferedWriter lexerWriter = new BufferedWriter(new FileWriter(lexerFile));) {
                lexerWriter.write(lexerSB.toString());
            }
        }
    }

    static void parse_analysis(InputStream inputStream) throws IOException {
        Lexer lexer = new Lexer(inputStream);
        Parser parser = new Parser(lexer);
        File parserFile = new File(FileConfig.outParser.getPath());
        StringBuilder parserSB = new StringBuilder();
        //遍历文件-语法分析
        SyntaxNode result = parser.parse();
        result.walk(terminalSymbol -> parserSB.append(terminalSymbol).append("\n"),
                nonTerminalSymbol -> {
                    String type = nonTerminalSymbol.getType();
                    if (!type.equals("BlockItem") && !type.equals("Decl") && !type.equals("BType")) {//除了这三种非终结符不打印
                        parserSB.append(nonTerminalSymbol).append("\n");
                    }
                });
        errorList.addAll(parser.getErrorList());
        if (errorList.isEmpty() && FileConfig.outParser.getOpen()) {//输出parser.txt
            try (BufferedWriter parserWriter = new BufferedWriter(new FileWriter(parserFile));) {
                parserWriter.write(parserSB.toString());
            }
        }
    }

    static void symbol_analysis(InputStream inputStream) throws IOException {
        File symbolFile = new File(FileConfig.outSymbol.getPath());
        StringBuilder symbolSB = new StringBuilder();
        Visitor visitor = new Visitor(errorList);
        Lexer lexer = new Lexer(inputStream);
        Parser parser = new Parser(lexer);

        SyntaxNode result = parser.parse();//完成parse建树
        visitor.visitCompUnitNode((CompUnitNode) result);
//        System.out.println(visitor.getCurrentSymbolTable());
        symbolSB.append(visitor.getCurrentSymbolTable());
        if (errorList.isEmpty() && FileConfig.outParser.getOpen()) {//输出parser.txt
            try (BufferedWriter symbolWriter = new BufferedWriter(new FileWriter(symbolFile));) {
                symbolWriter.write(symbolSB.toString());
            }
        }
    }


    private static void llvm_generate(InputStream inputStream) throws IOException {
        File llvmFile = new File(FileConfig.outLLVM.getPath());
        Visitor visitor = new Visitor(errorList);
        Lexer lexer = new Lexer(inputStream);
        Parser parser = new Parser(lexer);
        var out = new PrintStream(llvmFile);
        var result = parser.parse();
        var module = visitor.generateIR(result);


        out.print("""
                declare i32 @getint();
                declare i32 @getchar();
                declare void @putint(i32);
                declare void @putch(i32);
                declare void @putstr(i8*);
                                    
                """);
        module.dump(out);

    }

    private static Module optimize(Module module) {
        module = new DeadStoreElimination(module).pass();
        module = new DeadCodeElimination(module).pass();
        module = new PeepHole(module).pass();

        return module;
    }

    static void mips_generate(InputStream inputStream, boolean opti) throws IOException {
        File mipsFile = new File(FileConfig.outMIPS.getPath());
        Visitor visitor = new Visitor(errorList);
        Lexer lexer = new Lexer(inputStream);
        Parser parser = new Parser(lexer);
        PrintStream outMips = new PrintStream(mipsFile);
        if (errorList.isEmpty() && FileConfig.outParser.getOpen()) {
            SyntaxNode result = parser.parse();//完成parse建树
            var module = visitor.generateIR(result);
            module = optimize(module);
            var translator = new Translator();
            translator.translate(module);


            translator.getAsmTarget().dump(outMips, false);
        }
    }


    static void error_output() throws IOException {
        File errorFile = new File(FileConfig.outError.getPath());
        StringBuilder errorSB = new StringBuilder();
        //输出error.txt
        if (!errorList.isEmpty() && FileConfig.outError.getOpen()) {
            try (BufferedWriter errorWriter = new BufferedWriter(new FileWriter(errorFile))) {
                errorList.sort(Comparator.comparingInt(GramError::getLineno));
                for (GramError error : errorList) {
                    errorSB.append(error.getLineno()).append(" ").append(error.getCode()).append("\n");
                }
                errorWriter.write(errorSB.toString());
            }
        }
    }
}
